﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security;
using System.Security.Policy;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
using System.Runtime.Remoting.Messaging;
using System.Reflection;

namespace Lilium.T4.Hosting
{
    public class T4EngineHost : ITextTemplatingEngineHost
    {
        public T4EngineHost(string templateFile, T4HostOptions hostOptions)
        {
            if (hostOptions == null) throw new ArgumentNullException("hostOptions");

            m_TemplateFile = templateFile;
            m_HostOptions = hostOptions;
        }

        public string ProcessTemplate(IDictionary<string, object> arguments)
        {
            Reinitialize();

            var content = ReadFileContent(TemplateFile);
            try
            {
                LoadArguemnts(arguments);
                var result = Engine.ProcessTemplate(content, this);
                if (HasCompilerErrors)
                    throw new T4CompilerException(TemplateFile, GeneratedClassDefinition, CompilerErrors);
                return result;
            }
            finally
            {
                FreeArguemnts(arguments);
            }
        }

        #region ITextTemplatingEngineHost Members

        public object GetHostOption(string optionName)
        {
            return HostOptions.GetHostOption(optionName);
        }

        public bool LoadIncludeText(string requestFileName, out string content, out string location)
        {
            if (Path.IsPathRooted(requestFileName))
            {
                Zone zone = Zone.CreateFromUrl(new Uri(requestFileName).AbsoluteUri);
                if ((zone.SecurityZone == SecurityZone.Trusted) || (zone.SecurityZone == SecurityZone.MyComputer))
                {
                    content = ReadFileContent(requestFileName);
                    location = requestFileName;
                    return true;
                }
                this.LogError(false, string.Format(CultureInfo.CurrentCulture, "Unable to locate include file {0}.", new object[] { string.Format(CultureInfo.CurrentCulture, "The path '{0}' must be either local to this computer or part of your trusted zone.", new object[] { requestFileName }) }));
                content = string.Empty;
                location = string.Empty;
                return false;
            }
            List<string> list = new List<string> { Path.GetDirectoryName(this.TemplateFile) };
            /*
            foreach (string str in this.IncludeDirectories)
            {
                list.Add(str);
            }
             */
            foreach (string str2 in list)
            {
                string path = Path.Combine(str2, requestFileName);
                if (File.Exists(path))
                {
                    content = ReadFileContent(path);
                    location = path;
                    return true;
                }
            }
            this.LogError(false, string.Format(CultureInfo.CurrentCulture, "IncludeNotFound=Unable to locate include file {0}.", new object[] { requestFileName }));
            content = string.Empty;
            location = string.Empty;
            return false;
        }

        public void LogErrors(CompilerErrorCollection errors)
        {
            if (errors == null) throw new ArgumentNullException("errors");

            foreach (CompilerError error in errors)
            {
                if (!error.IsWarning)
                    HasCompilerErrors = true;
                CompilerErrors.Add(error);
            }
        }

        public AppDomain ProvideTemplatingAppDomain(string content)
        {
            GeneratedClassDefinition = content;
            OnTemplateClassGenerated(new TemplateClassGeneratedEventArgs(content));
            return AppDomain.CurrentDomain; //TODO: сделать виртуальный метод для генерации домена.
        }

        public string ResolveAssemblyReference(string assemblyReference)
        {
            string location;

            if (assemblyReference.StartsWith("file:///")) 
                assemblyReference = assemblyReference.Substring(8);

            if (Path.IsPathRooted(assemblyReference))
            {
                Zone zone = Zone.CreateFromUrl(new Uri(assemblyReference).AbsoluteUri);
                if ((zone.SecurityZone == SecurityZone.Trusted) || (zone.SecurityZone == SecurityZone.MyComputer))
                {
                    return assemblyReference;
                }
                this.LogError(false, string.Format(CultureInfo.CurrentCulture, "AssemblyNotFound=Unable to locate assembly {0}.", new object[] { string.Format(CultureInfo.CurrentCulture, "The path '{0}' must be either local to this computer or part of your trusted zone.", new object[] { assemblyReference }) }));
                return string.Empty;
            }
            /*
            using (StringEnumerator enumerator = this.ReferencePaths.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    location = Path.Combine(enumerator.Current, assemblyReference);
                    if (File.Exists(location))
                    {
                        return location;
                    }
                }
            }
            */
            location = GlobalAssemblyCacheHelper.GetLocation(assemblyReference);
            if (!string.IsNullOrEmpty(location))
            {
                return location;
            }
            return assemblyReference;
        }

        public Type ResolveDirectiveProcessor(string processorName)
        {
            var processor = this.HostOptions.GetDirectiveProcessor(processorName);
            if (processor != null)
                return processor;
            else
                throw new InvalidOperationException(string.Format("Directive processor {0} not found.", processorName));
        }

        public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
        {
            throw new NotImplementedException();
        }

        public string ResolvePath(string path)
        {
            if (Path.IsPathRooted(path))
            {
                return path;
            }
            string str2 = Path.Combine(Path.GetDirectoryName(this.TemplateFile), path);
            if (!File.Exists(str2) && !Directory.Exists(str2))
            {
                return path;
            }
            return str2;
        }

        public void SetFileExtension(string extension)
        {
            m_FileExtension = extension;
        }

        public void SetOutputEncoding(Encoding encoding, bool fromOutputDirective)
        {
            if (!this.EncodingSetFromOutputDirective)
            {
                if (fromOutputDirective)
                {
                    this.EncodingSetFromOutputDirective = true;
                    this.OutputEncoding = encoding;
                }
                else
                {
                    if ((this.OutputEncoding != null) && (encoding != this.OutputEncoding))
                    {
                        this.OutputEncoding = Encoding.UTF8;
                    }
                    this.OutputEncoding = encoding;
                }
            }
        }

        public IList<string> StandardAssemblyReferences
        {
            get
            {
                return HostOptions.StandardAssemblyReferences;
            }
        }

        public IList<string> StandardImports
        {
            get { return HostOptions.StandardImports; }
        }

        #region public string TemplateFile

        private readonly string m_TemplateFile;

        public string TemplateFile
        {
            get
            {
                return m_TemplateFile;
            }
        }

        #endregion

        #endregion

        #region public event EventHandler<TemplateClassGeneratedEventArgs> TemplateClassGenerated

        public event EventHandler<TemplateClassGeneratedEventArgs> TemplateClassGenerated;

        protected virtual void OnTemplateClassGenerated(TemplateClassGeneratedEventArgs args)
        {
            var handler = TemplateClassGenerated;
            if (handler != null)
                handler(this, args);
        }

        #endregion

        private void FreeArguemnts(IDictionary<string, object> arguments)
        {
            if (arguments != null)
                foreach (var key in arguments.Keys)
                    CallContext.FreeNamedDataSlot(key);
        }

        private void LoadArguemnts(IDictionary<string, object> arguments)
        {
            if (arguments != null)
                foreach (var arg in arguments)
                    CallContext.LogicalSetData(arg.Key, arg.Value);
        }

        private void LogError(bool isWarning, string message)
        {
            if (isWarning)
            {
                Console.Error.WriteLine(string.Format(CultureInfo.CurrentCulture, "Warning: {0}", new object[] { message }));
            }
            else
            {
                //base.ReturnCode = 1;
                Console.Error.WriteLine(string.Format(CultureInfo.CurrentCulture, "Warning: {0}", new object[] { message }));
            }
        }

        private static string ReadFileContent(string fileName)
        {
            using (StreamReader reader = new StreamReader(fileName))
            {
                return reader.ReadToEnd();
            }
        }

        private void Reinitialize()
        {
            m_GeneratedClassDefinition = null;
            CompilerErrors = new List<CompilerError>();
            HasCompilerErrors = false;
        }

        #region private CompilerErrorCollection CompilerErrors

        private List<CompilerError> m_CompilerErrors = new List<CompilerError>();

        private List<CompilerError> CompilerErrors
        {
            get
            {
                return m_CompilerErrors;
            }
            set
            {
                m_CompilerErrors = value;
            }
        }

        #endregion

        #region private bool EncodingSetFromOutputDirective

        private bool m_EncodingSetFromOutputDirective;

        private bool EncodingSetFromOutputDirective
        {
            get { return m_EncodingSetFromOutputDirective; }
            set { m_EncodingSetFromOutputDirective = value; }
        }

        #endregion

        #region private ITextTemplatingEngine Engine

        private readonly ITextTemplatingEngine m_Engine = new Engine();

        private ITextTemplatingEngine Engine
        {
            get
            {
                return m_Engine;
            }
        }

        #endregion

        #region private string FileExtension

        private string m_FileExtension;

        private string FileExtension
        {
            get { return m_FileExtension; }
            set { m_FileExtension = value; }
        }

        #endregion

        #region private bool HasCompilerErrors

        private bool m_HasCompilerErrors;

        private bool HasCompilerErrors
        {
            get { return m_HasCompilerErrors; }
            set { m_HasCompilerErrors = value; }
        }

        #endregion

        #region private T4HostOptions HostOptions

        private readonly T4HostOptions m_HostOptions;

        private T4HostOptions HostOptions
        {
            get
            {
                return m_HostOptions;
            }
        }

        #endregion

        #region private Encoding OutputEncoding

        private Encoding m_OutputEncoding;

        private Encoding OutputEncoding
        {
            get { return m_OutputEncoding; }
            set { m_OutputEncoding = value; }
        }

        #endregion

        #region private string GeneratedClassDefinition

        private string m_GeneratedClassDefinition;

        private string GeneratedClassDefinition
        {
            get { return m_GeneratedClassDefinition; }
            set { m_GeneratedClassDefinition = value; }
        }

        #endregion
    }
}